/*
 * @Author: Devin
 * @Date: 2022-04-24 16:06:53
 * @LastEditors: Devin
 * @LastEditTime: 2022-04-27 18:15:34
 */
import { isRef, ref } from 'vue';
import { hyphenate, isObject, isString } from '@vue/shared';
import { Loading } from './service';
import type { Directive, DirectiveBinding, UnwrapRef } from 'vue';
import type { LoadingOptions } from './types';
import type { LoadingInstance } from './loading';

const INSTANCE_KEY = Symbol('ElLoading');

export type LoadingBinding = boolean | UnwrapRef<LoadingOptions>;
export interface ElementLoading extends HTMLElement {
  [INSTANCE_KEY]?: {
    instance: LoadingInstance;
    options: LoadingOptions;
  };
}

const createInstance = (el: ElementLoading, binding: DirectiveBinding<LoadingBinding>) => {
  
  const vm: any = binding.instance;
  
  const getBindingProp = <K extends keyof LoadingOptions>(key: K): LoadingOptions[K] =>
    isObject(binding.value) ? binding.value[key] : undefined;

  const resolveExpression = (key: any) => {
    const data = (isString(key) && vm?.[key]) || key;
    if (data) return ref(data);
    else return data; 
  };

  const getProp = <K extends keyof LoadingOptions>(name: K) =>
    resolveExpression(getBindingProp(name) || el.getAttribute(`element-loading-${hyphenate(name)}`));

  const fullscreen = getBindingProp('fullscreen') ?? binding.modifiers.fullscreen;

  const options: LoadingOptions = {
    text: getProp('text'),
    svg: getProp('svg'),
    svgViewBox: getProp('svgViewBox'),
    spinner: getProp('spinner'),
    background: getProp('background'),
    customClass: getProp('customClass'),
    fullscreen,
    target: getBindingProp('target') ?? (fullscreen ? undefined : el),
    body: getBindingProp('body') ?? binding.modifiers.body,
    lock: getBindingProp('lock') ?? binding.modifiers.lock,
    render: getProp('render')
  };
  
  el[INSTANCE_KEY] = {
    options,
    instance: Loading(options)
  };
};

const updateOptions = (newOptions: any, originalOptions: any) => {
  for (const key of Object.keys(originalOptions)) {
    if (isRef(originalOptions[key])) originalOptions[key].value = newOptions[key];
  }
};

export const vLoading: Directive<ElementLoading, LoadingBinding> = {
  mounted(el, binding) {
    if (binding.value) {
      createInstance(el, binding);
    }
  },
  updated(el, binding) {
    const instance = el[INSTANCE_KEY];
    if (binding.oldValue !== binding.value) {
      if (binding.value && !binding.oldValue) {
        createInstance(el, binding);
      } else if (binding.value && binding.oldValue) {
        if (isObject(binding.value)) updateOptions(binding.value, instance!.options);
      } else {
        instance?.instance.close();
      }
    }
  },
  unmounted(el) {
    el[INSTANCE_KEY]?.instance.close();
  }
};
